﻿/*  Copyright 2012 PerceiveIT Limited
 *  This file is part of the Scryber library.
 *
 *  You can redistribute Scryber and/or modify 
 *  it under the terms of the GNU Lesser General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 * 
 *  Scryber is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU Lesser General Public License for more details.
 * 
 *  You should have received a copy of the GNU Lesser General Public License
 *  along with Scryber source code in the COPYING.txt file.  If not, see <http://www.gnu.org/licenses/>.
 * 
 */

using System;
using System.Collections.Generic;
using System.Text;
using Scryber.Text;
using Scryber.Native;
using System.Drawing;
using Scryber.Resources;

namespace Scryber.Drawing
{
    public delegate object ResolveStyleCallback(string styleClass);

    public partial class PDFGraphics
    {

        #region protected PDFFontResource CurrentFont {get; set;}

        private PDFFontResource _fontrsrc;

        /// <summary>
        /// Gets the current font (resource) associated with the Graphics context
        /// </summary>
        protected PDFFontResource CurrentFontResource
        {
            get { return _fontrsrc; }
            set { _fontrsrc = value; }
        }

        #endregion

        #region public PDFUnit CurrentFont {get; private set;}

        /// <summary>
        /// Gets the current font size
        /// </summary>
        public PDFFont CurrentFont
        {
            get;
            private set;
        }

        #endregion

        #region protected SetCurrentFont(PDFFont font)

        public void SetCurrentFont(PDFFont font)
        {
            PDFResource rsrc = this.Container.Document.GetResource(PDFResource.FontDefnResourceType, font.FullName, true);
            if (null == rsrc)
                throw new NullReferenceException(String.Format(Errors.FontNotFound, font.FullName));

            PDFName name = this.Container.Register(rsrc);
            this.CurrentFontResource = (PDFFontResource)rsrc;
            this.CurrentFont = font;

            if(null != this.Writer)
                this.Writer.WriteOpCodeS(PDFOpCode.TxtFont, name, font.Size.RealValue);
            
        }

        #endregion

        #region protected RenderCurrentTextMode(TextRenderMode mode)

        protected void RenderCurrentTextMode(TextRenderMode mode)
        {
            this.Writer.WriteOpCodeS(PDFOpCode.TxtRenderMode, new PDFNumber((int)mode));
        }

        #endregion



        /// <summary>
        /// Measures a single line of text (no wrapping) and returns the size required, 
        /// and the number of characters fitted into that line - using GDI+, slow but does not require the font file
        /// </summary>
        /// <param name="chars"></param>
        /// <param name="available"></param>
        /// <param name="options"></param>
        /// <param name="charsfitted"></param>
        /// <returns></returns>
        protected PDFSize MeasureGraphicsString(string chars, PDFSize available, WordWrap wrapping, out int charsfitted)
        {
            //PDFUnit h = options.Leading.HasValue? options.Leading.Value : options.Font.Size;
            //PDFUnit w = chars.Length * h.PointsValue;
            //charsfitted = chars.Length;
            //return new PDFSize(w,h);

            if(_wingraphics == null)
                _wingraphics = this.CreateWinGraphics();
            if (available.Width <= 1)
            {
                charsfitted = 0;
                return PDFSize.Empty;
            }
            else
            {
                StringFormat format = GetStringFormatting(wrapping);

                System.Drawing.SizeF size = new SizeF((float)available.Width.PointsValue, (float)available.Height.PointsValue);
                System.Drawing.Font sysfont = this.CurrentFont.GetSystemFont();
                int linesfitted;
                if (wrapping == WordWrap.NoWrap)
                {
                    size = _wingraphics.MeasureString(chars, sysfont, PointF.Empty, format);
                    charsfitted = chars.Length;
                }
                else
                    size = _wingraphics.MeasureString(chars, sysfont, size, format, out charsfitted, out linesfitted);

                return new PDFSize(size.Width, size.Height);
            }

        }

        /// <summary>
        /// All the character widths used
        /// </summary>
        /// <param name="chars"></param>
        /// <param name="frsrc"></param>
        private void RegisterStringUse(string chars, int startIndex, int endIndex)
        {
            PDFFontResource frsrc = this.CurrentFontResource;
            if (frsrc.Widths != null) 
                frsrc.Widths.RegisterGlyphs(chars, startIndex, endIndex);
        }

        /// <summary>
        /// Measures a single line of text (no wrapping) and returns the size required, 
        /// and the number of characters fitted into that line. 
        /// </summary>
        /// <param name="chars"></param>
        /// <param name="startIndex"></param>
        /// <param name="available"></param>
        /// <param name="options"></param>
        /// <param name="charsfitted"></param>
        /// <returns></returns>
        /// <remarks>Uses the TTFFontFile if available, otherwise will default to using the GDI+ measure,
        /// which is slower but supports postscript fonts</remarks>
        public PDFSize MeasureString(string chars, int startIndex, PDFSize available, WordWrap wrapping, out int charsfitted)
        {
            
            PDFFontResource frsc = this.CurrentFontResource;
            PDFUnit fsize = this.CurrentFont.Size;

            if (null == this.CurrentFontResource)
                throw new InvalidOperationException("Current font has not be set on the graphics class, so strings cannot be measured");

            PDFFontDefinition defn = this.CurrentFontResource.Definition;
            PDFSize measured;
            if (defn.CanMeasureStrings)
            {
                bool trimtoword = true;
                if (wrapping == WordWrap.NoWrap)
                    available.Width = new PDFUnit(int.MaxValue, PageUnits.Points);
                else if (wrapping == WordWrap.Character)
                    trimtoword = false;


                measured = defn.MeasureStringWidth(chars, startIndex, fsize.PointsValue, available.Width.PointsValue, trimtoword, out charsfitted);
                if (charsfitted > 0)
                    this.RegisterStringUse(chars, startIndex, charsfitted);
            }
            else
            {
                if (startIndex > 0)
                    chars = chars.Substring(startIndex);
                measured = this.MeasureGraphicsString(chars, available, wrapping, out charsfitted);
            }

            return measured;
        }

        private double GetLineLeft(PDFSize size, PDFTextRenderOptions options)
        {
            if (options != null && options.FirstLineInset != PDFUnit.Empty)
                return options.FirstLineInset.Value.ToPoints().Value;
            else
                return 0.0;
        }

        private System.Drawing.Font GetSystemFont(PDFTextRenderOptions options)
        {
            if (options == null || options.Font == null)
                return null;
            else
                return options.Font.GetSystemFont();
            
        }

        private PDFTextRenderOptions ResolveTextClass(string classname)
        {
            throw new NotImplementedException("ResolveTextClass");
        }

        private void ReleaseTextRenderOptions(PDFTextRenderOptions options, PDFRect bounds)
        {
            if (options.FillBrush != null)
                options.FillBrush.ReleaseGraphics(this, bounds);
            if (options.Stroke != null)
                options.Stroke.ReleaseGraphics(this, bounds);
        }

        public TextRenderMode SetTextRenderOptions(PDFTextRenderOptions options, PDFRect bounds)
        {
            
            TextRenderMode mode = TextRenderMode.NoOp;
            if (options == null)
                throw new ArgumentNullException("options");
            
            if (options.FillBrush != null)
            {
                mode = TextRenderMode.Fill;
                options.FillBrush.SetUpGraphics(this, bounds);
            }
            if (options.Stroke != null)
            {
                if (mode == TextRenderMode.Fill)
                    mode = TextRenderMode.FillAndStroke;
                else
                    mode = TextRenderMode.Stroke;

                options.Stroke.SetUpGraphics(this, bounds);
            }
            if (options.Font != null)
                this.SetCurrentFont(options.Font);

            SetTextLeading(options);
            
            SetTextRenderMode(mode);
            return mode;
        }

        public void SetTextLeading(PDFTextRenderOptions options)
        {
            PDFUnit h = options.GetLineHeight();
            this.Writer.WriteOpCodeS(PDFOpCode.TxtLeading, h.RealValue);
        }

        public void SetTextRenderMode(TextRenderMode mode)
        {
            this.Writer.WriteOpCodeS(PDFOpCode.TxtRenderMode, (PDFNumber)(int)mode);
        }


        public void BeginText()
        {
            this.Writer.WriteOpCodeS(PDFOpCode.TxtBegin);
        }

        public void EndText()
        {
            this.Writer.WriteOpCodeS(PDFOpCode.TxtEnd);
        }

        public void FillText(string chars, int offset, int count)
        {
            if (!this.Writer.UseHex)
            {
                if (chars.IndexOfAny(TextConst.EscapeChars, offset,count) > -1)
                {
                    //cannot optimize the replacement of escape characters so substring and return
                    this.FillText(chars.Substring(offset, count));
                    return;
                }
            }

            string glyphs = this.CurrentFontResource.Widths.RegisterGlyphs(chars, offset, count);

            

            this.Writer.WriteStringLiteralS(glyphs, this.CurrentFontResource.Encoding);
            this.Writer.WriteOpCodeS(PDFOpCode.TxtPaint);
        }

        public void FillText(string chars)
        {
            if (!this.Writer.UseHex)
            {
                if (chars.IndexOfAny(TextConst.EscapeChars) > -1)
                {
                    for (int i = 0; i < TextConst.EscapeStrings.Length; i++)
                    {
                        chars = chars.Replace(TextConst.EscapeStrings[i], TextConst.ReplaceStrings[i]);
                    }
                }
            }

            string glyphs = this.CurrentFontResource.Widths.RegisterGlyphs(chars);

            this.Writer.WriteStringLiteralS(glyphs, this.CurrentFontResource.Encoding);
            this.Writer.WriteOpCodeS(PDFOpCode.TxtPaint);
        }


        /// <summary>
        /// Moves the current text cursor along by the specified amount. 
        /// If Absolute then this is from the top left of the current page. 
        /// If not absolute, then this is relative to the current cursor position.
        /// </summary>
        /// <param name="offset"></param>
        /// <param name="absolute"></param>
        public void MoveTextCursor(PDFSize offset, bool absolute)
        {
            if (absolute)
            {
                this.Writer.WriteOpCodeS(PDFOpCode.TxtMoveNextOffset, GetXPosition(offset.Width), GetYPosition(offset.Height));
            }
            else if (offset == PDFSize.Empty)
            {
                this.Writer.WriteOpCodeS(PDFOpCode.TxtNextLine);
            }
            else
            {
                this.Writer.WriteOpCodeS(PDFOpCode.TxtMoveNextOffset, GetXOffset(offset.Width), GetYOffset(offset.Height));
            }
        }


        
        /// <summary>
        /// String format to stop the wrapping of text so a single string can be measured
        /// </summary>
        private static readonly System.Drawing.StringFormat NoWrappingWordFormat = InitStringFormat(StringTrimming.Word);

        private static readonly StringFormat NoWrappingCharacterFormant = InitStringFormat(StringTrimming.Character);

        #region private static System.Drawing.StringFormat InitStringFormat(StringTrimming triming)

        private static System.Drawing.StringFormat InitStringFormat(StringTrimming triming)
        {

#if USE_NON_TYPOGRAPHIC

            //Shaves 20% off measure string time with less accuracy
            StringFormat format = StringFormat.GenericDefault.Clone() as StringFormat;
#else
            StringFormat format = StringFormat.GenericTypographic.Clone() as StringFormat;
#endif
            format.Alignment = StringAlignment.Near;
            format.FormatFlags = StringFormatFlags.NoWrap | StringFormatFlags.MeasureTrailingSpaces;
            format.Trimming = triming;
            return format;
        }

        #endregion

        #region private static StringFormat GetStringFormatting(PDFTextRenderOptions options)

        private static StringFormat GetStringFormatting(PDFTextRenderOptions options)
        {
            StringFormat format;
            if (options.WrapText.HasValue && options.WrapText.Value == WordWrap.Character)
                format = NoWrappingCharacterFormant;
            else
                format = NoWrappingWordFormat;
            return format;
        }

        #endregion

        #region private static StringFormat GetStringFormatting(WordWrap wrapping)

        private static StringFormat GetStringFormatting(WordWrap wrapping)
        {
            StringFormat format;
            if (wrapping == WordWrap.Character)
                format = NoWrappingCharacterFormant;
            else
                format = NoWrappingWordFormat;
            return format;
        }

        #endregion

    }
}
